/***************************************************************
 *
 * (c) 2008 Markus Dittrich 
 *
 * This program is free software; you can redistribute it 
 * and/or modify it under the terms of the GNU General Public 
 * License Version 3 as published by the Free Software Foundation. 
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License Version 3 for more details.
 *
 * You should have received a copy of the GNU General Public 
 * License along with this program; if not, write to the Free 
 * Software Foundation, Inc., 59 Temple Place - Suite 330, 
 * Boston, MA 02111-1307, USA.
 *
 ****************************************************************/

/* C includes */
#include <cstdlib>
#include <errno.h>
#include <iostream>

/* Qt includes */
#include <QStringList>

/** local includes */
#include "basic_definitions.h"
#include "utilities.h"

/* pull a few names into namespace */
using std::vector;
using std::find;


//---------------------------------------------------------------
// splits a string into a vector of doubles; splitting is done
// at a given separator or space by default;
// the maxLength parameter determines the maximum length of the
// created container.
// We use errno to discover any potential problem with strtod
//---------------------------------------------------------------
bool string_to_doubles(std::vector<double>& container,
  std::string& aString, int maxLength, Separator aSeparator)
{
  Tokenizer tokens(aString,aSeparator);

  int counter = 0;
  bool success = true;
  FOREACH(std::string token, tokens)
  {
    if ( ++counter > maxLength )
    {
      break;
    }

    errno = 0;
    double tokenAsDouble = strtod(token.c_str(),NULL);
    if ( errno != 0 )
    {
      success = false;
    }
    
    container.push_back(tokenAsDouble);
  }

  return success;
}


//--------------------------------------------------------------
// given the x and y coordinate as doubles generate a string
// for display in the main window 
//--------------------------------------------------------------
QString generate_coordinate_string(double x, double y)
{
  QString xString;
  xString.setNum(x);
  QString yString;
  yString.setNum(y);

  QString result = "X : " + xString + "   " + "Y : " + yString;

  return result;
}



//--------------------------------------------------------------
// formats a double value based on its precision and returns
// it as a QString
//--------------------------------------------------------------
QString pretty_format_double(double number)
{
  double absValue = fabs(number);

  int fieldWidth = 5;
  char format = 'f';
  if ( absValue <= 1e-4 || absValue >= 1e4 )
  {
    fieldWidth = 2;
    format = 'e';
  }
  else if ( absValue > 100 )
  {
    fieldWidth = 3;
  }
  else if ( absValue > 10 )
  {
    fieldWidth = 4;
  }
  
  int paddingRight = -6;
  return QString("%1").arg(number,paddingRight,format, fieldWidth,'0');
}



/*****************************************************************
 * special min/max/mean/median functions needed for histogram
 * data sets since in this case we store the coordinate to
 * draw the bars rather than some raw data
 *****************************************************************/

//--------------------------------------------------------------
// convert histogram locations into vector of bar locations
// where location is the center of a bar
//--------------------------------------------------------------
void extract_bin_locations(const vector<double>& histogramXData, 
    vector<double>& binLocations)
{
  /* grab 1st and 2nd element of each bar; they're average 
   * are our bin centers */
  vector<double>::size_type numCenters = 
    histogramXData.size()/POINTS_PER_HISTOGRAM_BIN;
  binLocations.reserve(numCenters+1);

  double binWidth = extract_histogram_bin_width(histogramXData); 
  double halfBinWidth = 0.5 * binWidth;

  double binCenters = 0.0;
  unsigned int counter = 0;
  vector<double>::const_iterator pos = histogramXData.begin();
  while ( counter < numCenters )
  {
    binCenters = *(pos+1) + halfBinWidth; 
    binLocations.push_back(binCenters);
    pos += POINTS_PER_HISTOGRAM_BIN;
    ++counter;
  }
}


//--------------------------------------------------------------
// convert histogram data into raw data set of bar heights
//--------------------------------------------------------------
void extract_bin_heights(const vector<double>& histogramYData, 
    vector<double>& binHeights)
{
  /* grab every 5th element starting from the 2nd, aka
   * the height of our bars */
  vector<double>::size_type numBars = 
    histogramYData.size()/POINTS_PER_HISTOGRAM_BIN;
  binHeights.reserve(numBars+1);

  double barHeights = 0.0;
  unsigned int counter = 0;
  vector<double>::const_iterator pos = histogramYData.begin();
  while ( counter < numBars )
  {
    barHeights = *(pos+1);
    binHeights.push_back(barHeights);
    pos += POINTS_PER_HISTOGRAM_BIN;
    ++counter;
  }
}


//--------------------------------------------------------------
// extract the bin width from the data set
//--------------------------------------------------------------
double extract_histogram_bin_width(
    const vector<double>& histogramXData)
{
  double width = 0.0;

  /* we need at least a single bar */
  vector<double>::size_type numBars = 
    histogramXData.size()/POINTS_PER_HISTOGRAM_BIN;

  if ( numBars == 0 ) 
  {
    return width;
  }

  width = histogramXData[2] - histogramXData[1];

  return width;
}


//--------------------------------------------------------------
// extract the total number of elements in all bins of the
// histogram
// NOTE: in case the histogram is normalized we return
// numElements = 1 
//--------------------------------------------------------------
int extract_total_bin_element_count(bool normalized,
    const vector<double>& histogramYData)
{
  vector<double> binHeights;
  extract_bin_heights(histogramYData, binHeights);

  if ( normalized )
  {
    return 1;
  }
  else
  {
    return std::accumulate(binHeights.begin(),binHeights.end(),0.0);
  }
}


//--------------------------------------------------------------
// location of data max 
//--------------------------------------------------------------
bool histogram_max(const vector<double>& binSize, double& histMax)
{
  return data_max(binSize.begin(),binSize.end(),histMax);
}


//--------------------------------------------------------------
// value of lowest bar
//--------------------------------------------------------------
bool histogram_min(const vector<double>& binSize, double& histMin)
{
  return data_min(binSize.begin(),binSize.end(),histMin);
}


//--------------------------------------------------------------
// mean value of bar heights
//--------------------------------------------------------------
bool histogram_mean(const vector<double>& binLocations,
    const vector<double>& binSize, int numElements, double& histMean)
{
  assert( binLocations.size() == binSize.size() );

  double mean = 0.0;
  for ( unsigned int counter = 0; counter < binSize.size(); ++counter)
  {
    mean += binLocations[counter] * binSize[counter];
  }

  histMean = mean/numElements;

  return true;
}


//--------------------------------------------------------------
// median value of bar heights
// NOTE: for histograms it is not clear what should be done
// for an even number of elements since the bin positions are
// discrete and it does not seem to make sense to average over
// neighboring bins. Hence, we always define the median bin
// to be the one which contains element numBins/2
//--------------------------------------------------------------
bool histogram_median(const vector<double>& binLocations,
    const vector<double>& binSize, int numElements, double& histMedian)
{
  assert( binLocations.size() == binSize.size() );
  
  double medianElement = 0.5 * numElements;
  double accumulator = 0.0;
 
  for ( unsigned int counter = 0; counter < binSize.size(); ++ counter)
  {
    accumulator += binSize[counter];

    if ( accumulator >= medianElement )
    {
      histMedian = binLocations[counter];
      return true;
    }
  }

  return false;
}

